package com.appbee.core.server.dao;


import java.lang.reflect.ParameterizedType;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.inject.Inject;

import com.appbee.core.server.dao.access.CrudAccessControl;
import com.appbee.core.server.dao.access.IUserControl;
import com.appbee.core.server.model.HasLiveState;
import com.appbee.core.shared.model.HasCreateDateTime;
import com.appbee.core.shared.model.HasCreateUser;
import com.appbee.core.shared.model.HasEditDateTime;
import com.appbee.core.shared.model.HasEditUser;
import com.appbee.core.shared.model.HasLongId;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.code.twig.FindCommand.RootFindCommand;
import com.google.code.twig.ObjectDatastore;

public class BaseDAO<T extends HasLongId>
{
	private static final Logger log = Logger.getLogger(BaseDAO.class.getName());
	protected ObjectDatastore objectDatastore;
	private final Class<T> persistentClass;
	protected CrudAccessControl accessControl;
	protected IUserControl userControl;
	
	@SuppressWarnings("unchecked")
	protected BaseDAO()
	{
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
		
	}
	
	public Class<T> getPersistentClass()
	{
		return persistentClass;
	}
	
	public ObjectDatastore getObjectDatastore()
	{
		return objectDatastore;
	}

	@Inject
	public void setObjectDatastore(ObjectDatastore objectDatastore)
	{
		this.objectDatastore = objectDatastore;
	}

	@Inject
	public void setUserControl(IUserControl userControl)
	{
		this.userControl = userControl;
	}

	@Inject
	public void setAccessControl(CrudAccessControl accessControl)
	{
		this.accessControl = accessControl;

	}

	public CrudAccessControl getAccessControl()
	{
		return this.accessControl;
	}

	public void store(T entity)
	{
		try
		{
			getAccessControl().assertCanInsert(entity);
			setEditDateTime(entity);
			setEditUser(entity);
			if(entity instanceof HasCreateDateTime)
			{
				((HasCreateDateTime)entity).setCreateDateTime(new Date());
			}
			if (entity instanceof HasCreateUser)
			{
				((HasCreateUser) entity).setCreateUserEmail(userControl
						.getCurrentUserEmail());
			}
			log.info("store");
			objectDatastore.store(entity);
			log.info("Id: "+entity.getId());
		}
		catch (Throwable e)
		{
			log.throwing(getClass().getName(), "update", e);
			throw new RuntimeException(e);
		}
	}

	private void setEditDateTime(T entity)
	{
		if(entity instanceof HasEditDateTime)
		{
			((HasEditDateTime)entity).setEditDateTime(new Date());
		}
	}
	
	public void update(T entity)
	{
		try
		{
			accessControl.assertCanUpdate(entity);
			setEditDateTime(entity);
			setEditUser(entity);
			log.info("update id: "+entity.getId());
			objectDatastore.associate(entity);
			objectDatastore.update(entity);
			log.info("update done id: "+entity.getId());
		}
		catch (Throwable e)
		{
			log.throwing(getClass().getName(), "update", e);
			throw new RuntimeException(e);
		}
	}

	public void updateWithOutUserAndTimestamp(T entity)
	{
		try
		{
			accessControl.assertCanUpdate(entity);
			log.info("update id: " + entity.getId());
			objectDatastore.update(entity);
			log.info("update done id: " + entity.getId());
		}
		catch (Throwable e)
		{
			log.throwing(getClass().getName(), "update", e);
			throw new RuntimeException(e);
		}
	}

	private void setEditUser(T entity)
	{
		if (entity instanceof HasEditUser)
		{
			((HasEditUser) entity).setEditUserEmail(userControl
					.getCurrentUserEmail());
		}
	}
	
	public void save(T entity)
	{
		if(entity.getId()==null)
		{
			store(entity);
		}
		else
		{
			update(entity);
		}
	}
	
	public T findById(long id)
	{
		log.info(persistentClass.getName() + " findById: " + id);
		T value = objectDatastore.load(persistentClass, id);
		if (value != null)
		{
			log.info("Found: " + value.toString());
			accessControl.assertCanRead(value);
		}
		return value;
	}
	
	public void addDefaultOrderBy(RootFindCommand<T> findCommand)
	{
	}
	
	public List<T> findByRange(int start,int size)
	{
		log.info("findByRagne: start: "+start+" size: "+size);
		accessControl.assertCanReadType(getPersistentClass());
		RootFindCommand<T> findCommand = objectDatastore.find().type(
				persistentClass);
		addDefaultOrderBy(findCommand);
		QueryResultIterator<T> iterator = findCommand.startFrom(start)
				.fetchMaximum(size).now();
		return Lists.newArrayList(iterator);
	}

	public List<T> findAll()
	{
		log.info("findAll");
		accessControl.assertCanReadType(getPersistentClass());
		RootFindCommand<T> findCommand = objectDatastore.find()
				.type(persistentClass);
		addDefaultOrderBy(findCommand);
		QueryResultIterator<T> iterator = findCommand.now();
		return Lists.newArrayList(iterator);
	}
	
	public Integer totalSize()
	{
		return objectDatastore.find().type(persistentClass).returnCount().now();
	}

	public void saveAllTransaction(T bean)
	{
		log.info("Save transaction, id: "+bean.getId());
		Transaction transaction = objectDatastore.beginTransaction();
		try
		{
			save(bean);
			transaction.commit();
		}
		catch (Throwable ex) 
		{
			log.throwing(getClass().getName(), "saveAllTransaction", ex);
			if(transaction.isActive())
			{
				transaction.rollback();
			}
			throw new RuntimeException(ex);
		}
	}

	public void delete(T obj)
	{
		accessControl.assertCanDelete(obj);
		log.info("delete id: " + obj.getId());
		objectDatastore.associate(obj);
		objectDatastore.delete(obj);
		if (obj instanceof HasLiveState)
		{
			((HasLiveState) obj).setLive(false);
		}
	}
	
}
